const { getType } = require('../../common/utils');
const crypto = require('crypto');
const createConfig = require('uni-config-center');
const shareConfig = createConfig({
    pluginId: 'uni-id',
});
let customPassword = {};
if (shareConfig.hasFile('custom-password.js')) {
    customPassword = shareConfig.requireFile('custom-password.js') || {};
}

const passwordAlgorithmMap = {
    UNI_ID_HMAC_SHA1: 'hmac-sha1',
    UNI_ID_HMAC_SHA256: 'hmac-sha256',
    UNI_ID_CUSTOM: 'custom',
};

const passwordAlgorithmKeyMap = Object.keys(passwordAlgorithmMap).reduce((res, item) => {
    res[passwordAlgorithmMap[item]] = item;
    return res;
}, {});

const passwordExtMethod = {
    [passwordAlgorithmMap.UNI_ID_HMAC_SHA1]: {
        verify({ password }) {
            const { password_secret_version: passwordSecretVersion } = this.userRecord;

            const passwordSecret = this._getSecretByVersion({
                version: passwordSecretVersion,
            });

            const { passwordHash } = this.encrypt({
                password,
                passwordSecret,
            });

            return passwordHash === this.userRecord.password;
        },
        encrypt({ password, passwordSecret }) {
            const { value: secret, version } = passwordSecret;
            const hmac = crypto.createHmac('sha1', secret.toString('ascii'));

            hmac.update(password);

            return {
                passwordHash: hmac.digest('hex'),
                version,
            };
        },
    },
    [passwordAlgorithmMap.UNI_ID_HMAC_SHA256]: {
        verify({ password }) {
            const parse = this._parsePassword();
            const passwordHash = crypto.createHmac(parse.algorithm, parse.salt).update(password).digest('hex');

            return passwordHash === parse.hash;
        },
        encrypt({ password, passwordSecret }) {
            const { version } = passwordSecret;

            // 默认使用 sha256 加密算法
            const salt = crypto.randomBytes(10).toString('hex');
            const sha256Hash = crypto
                .createHmac(passwordAlgorithmMap.UNI_ID_HMAC_SHA256.substring(5), salt)
                .update(password)
                .digest('hex');
            const algorithm = passwordAlgorithmKeyMap[passwordAlgorithmMap.UNI_ID_HMAC_SHA256];
            // B 为固定值，对应 PasswordMethodMaps 中的 sha256算法
            // hash 格式 $[PasswordMethodFlagMapsKey]$[salt size]$[salt][Hash]
            const passwordHash = `$${algorithm}$${salt.length}$${salt}${sha256Hash}`;

            return {
                passwordHash,
                version,
            };
        },
    },
    [passwordAlgorithmMap.UNI_ID_CUSTOM]: {
        verify({ password, passwordSecret }) {
            if (!customPassword.verifyPassword)
                throw new Error('verifyPassword method not found in custom password file');

            // return true or false
            return customPassword.verifyPassword({
                password,
                passwordSecret,
                userRecord: this.userRecord,
                clientInfo: this.clientInfo,
            });
        },
        encrypt({ password, passwordSecret }) {
            if (!customPassword.encryptPassword)
                throw new Error('encryptPassword method not found in custom password file');

            // return object<{passwordHash: string, version: number}>
            return customPassword.encryptPassword({
                password,
                passwordSecret,
                clientInfo: this.clientInfo,
            });
        },
    },
};

class PasswordUtils {
    constructor({ userRecord = {}, clientInfo, passwordSecret } = {}) {
        if (!clientInfo) throw new Error('Invalid clientInfo');
        if (!passwordSecret) throw new Error('Invalid password secret');

        this.clientInfo = clientInfo;
        this.userRecord = userRecord;
        this.passwordSecret = this.prePasswordSecret(passwordSecret);
    }

    /**
     * passwordSecret 预处理
     * @param passwordSecret
     * @return {*[]}
     */
    prePasswordSecret(passwordSecret) {
        const newPasswordSecret = [];
        if (getType(passwordSecret) === 'string') {
            newPasswordSecret.push({
                value: passwordSecret,
                type: passwordAlgorithmMap.UNI_ID_HMAC_SHA1,
            });
        } else if (getType(passwordSecret) === 'array') {
            for (const secret of passwordSecret.sort((a, b) => a.version - b.version)) {
                newPasswordSecret.push({
                    ...secret,
                    // 没有 type 设置默认 type hmac-sha1
                    type: secret.type || passwordAlgorithmMap.UNI_ID_HMAC_SHA1,
                });
            }
        } else {
            throw new Error('Invalid password secret');
        }

        return newPasswordSecret;
    }

    /**
     * 获取最新加密密钥
     * @return {*}
     * @private
     */
    _getLastestSecret() {
        return this.passwordSecret[this.passwordSecret.length - 1];
    }

    _getOldestSecret() {
        return this.passwordSecret[0];
    }

    _getSecretByVersion({ version } = {}) {
        if (!version && version !== 0) {
            return this._getOldestSecret();
        }
        if (this.passwordSecret.length === 1) {
            return this.passwordSecret[0];
        }
        return this.passwordSecret.find((item) => item.version === version);
    }

    /**
     * 获取密码的验证/加密方法
     * @param passwordSecret
     * @return {*[]}
     * @private
     */
    _getPasswordExt(passwordSecret) {
        const ext = passwordExtMethod[passwordSecret.type];
        if (!ext) {
            throw new Error(`暂不支持 ${passwordSecret.type} 类型的加密算法`);
        }

        const passwordExt = Object.create(null);

        for (const key in ext) {
            passwordExt[key] = ext[key].bind(
                Object.assign(
                    this,
                    Object.keys(ext).reduce((res, item) => {
                        if (item !== key) {
                            res[item] = ext[item].bind(this);
                        }
                        return res;
                    }, {})
                )
            );
        }

        return passwordExt;
    }

    _parsePassword() {
        const [algorithmKey = '', cost = 0, hashStr = ''] = this.userRecord.password.split('$').filter((key) => key);
        const algorithm = passwordAlgorithmMap[algorithmKey] ? passwordAlgorithmMap[algorithmKey].substring(5) : null;
        const salt = hashStr.substring(0, Number(cost));
        const hash = hashStr.substring(Number(cost));

        return {
            algorithm,
            salt,
            hash,
        };
    }

    /**
     * 生成加密后的密码
     * @param {String} password 密码
     */
    generatePasswordHash({ password }) {
        if (!password) throw new Error('Invalid password');

        const passwordSecret = this._getLastestSecret();
        const ext = this._getPasswordExt(passwordSecret);

        const { passwordHash, version } = ext.encrypt({
            password,
            passwordSecret,
        });

        return {
            passwordHash,
            version,
        };
    }

    /**
     * 密码校验
     * @param {String} password
     * @param {Boolean} autoRefresh
     * @return {{refreshPasswordInfo: {version: *, passwordHash: *}, success: boolean}|{success: boolean}}
     */
    checkUserPassword({ password, autoRefresh = true }) {
        if (!password) throw new Error('Invalid password');

        const { password_secret_version: passwordSecretVersion } = this.userRecord;
        const passwordSecret = this._getSecretByVersion({
            version: passwordSecretVersion,
        });
        const ext = this._getPasswordExt(passwordSecret);

        const success = ext.verify({ password, passwordSecret });

        if (!success) {
            return {
                success: false,
            };
        }

        let refreshPasswordInfo;
        if (autoRefresh && passwordSecretVersion !== this._getLastestSecret().version) {
            refreshPasswordInfo = this.generatePasswordHash({ password });
        }

        return {
            success: true,
            refreshPasswordInfo,
        };
    }
}

module.exports = PasswordUtils;
